package cate.game.fight.test;

import cate.common.table.d.GDBuff;
import cate.common.table.d.GDCamp;
import cate.common.table.d.GDFight;
import cate.common.table.d.GDHero;
import cate.common.table.fight.buff.row.BuffRow;
import cate.common.table.item.hero.row.HeroBaseRow;
import cate.game.GameBody;
import cate.game.play.fighter.FighterPerformance;
import cate.game.play.fighter.HeroFighter;
import cate.game.play.part.BattleHero;
import cate.game.play.proce.play.PlayPO;
import lombok.Getter;

import java.util.*;
import java.util.stream.Collectors;

public class FightTestStat {

	@Getter
	private int fightNum;

	@Getter
	private int totalRound;

	private Map<Integer,HeroStat> map = new HashMap<>();

	private HeroStat getHeroStat(GameBody game,int protoId) {
		return map.computeIfAbsent(protoId, h -> new HeroStat(game,protoId));
	}

	public void record(GameBody game, PlayPO play) {
		fightNum++;
		totalRound += play.rounds.size();
		boolean winA = play.winner == GDFight.Team.A;

		int focusStar = play.teamBegin.a.getAllHero().stream().sorted(Comparator.comparing(f -> -f.star)).findFirst().get().star;

		List<HeroFighter> teamAFighters = play.teamEnd.a.getAllHero().stream().filter(f -> f.star == focusStar).collect(Collectors.toList());
		List<HeroFighter> teamBFighters = play.teamEnd.b.getAllHero().stream().filter(f -> f.star == focusStar).collect(Collectors.toList());

		Set<Integer> teamAId = teamAFighters.stream().map(f -> f.protoId).collect(Collectors.toSet());
		Set<Integer> teamBId = teamBFighters.stream().map(f -> f.protoId).collect(Collectors.toSet());

		teamAFighters.removeIf(f->teamBId.contains(f.protoId));
		teamBFighters.removeIf(f->teamAId.contains(f.protoId));

		for (HeroFighter hero : teamAFighters) {
			HeroStat stat = getHeroStat(game, hero.protoId);
			if (stat.power == 0) {
				for (BattleHero h : play.a.hs) {
					if (h.hero.tid == hero.heroTid) {
						stat.power = h.power;
						break;
					}
				}
			}
			stat.record(game, winA, hero);
		}
		for (HeroFighter hero : teamBFighters) {
			HeroStat stat = getHeroStat(game, hero.protoId);
			if (stat.power == 0) {
				for (BattleHero h : play.b.hs) {
					if (h.hero.tid == hero.heroTid) {
						stat.power = h.power;
						break;
					}
				}
			}
			stat.record(game, !winA, hero);
		}
	}

	public List<TestStat> buildResult() {
		for (HeroStat value : map.values()) {
			value.stat();
		}
		List<TestStat> result = new ArrayList<>();
		int rank = 0;
		for (HeroStat f : map.values().stream().sorted(Comparator.comparing(s -> -s.winRate)).collect(Collectors.toList())) {
			rank++;
			TestStat stat = new TestStat();
			stat.rank = String.valueOf(rank);
			stat.name = f.name;
			stat.round = String.valueOf(f.round);
			stat.winRate = String.format("%.2f", f.winRate) + "%";
			stat.talent = String.valueOf(f.talent);
			stat.power = formatNum(f.power);
			stat.camp = GDCamp.Type.description(f.camp);
			stat.job = GDHero.Job.description(f.job);
			stat.avgDamage = formatNum(f.avgDamage);
			stat.avgHeal = formatNum(f.avgHeal);
			stat.avgHurt = formatNum(f.avgHurt);
			stat.avgKill = String.format("%.2f", f.avgKill);
			stat.avgDead = String.format("%.2f", f.avgDead);
			stat.avgMove = String.format("%.2f", f.avgMove);
			stat.avgCtrl = String.format("%.2f", f.avgCtrl);
			stat.avgAlive = String.format("%.2f", f.avgAlive) + "%";
			result.add(stat);
		}
		return result;
	}

	private static final double K = 1_000d;

	private static final double MILLION = 1000_000d;

	private static final double BILLION = 1000_000_000d;

	private static final double TRILLION = 1000_000_000_000d;

	public static String formatNum(double num) {
		if (num >= TRILLION) {
			return String.format("%.2f", num / TRILLION) + "T";
		}
		if (num > BILLION) {
			return String.format("%.2f", num / BILLION) + "B";
		}
		if (num > MILLION) {
			return String.format("%.2f", num / MILLION) + "M";
		}
		if (num > K) {
			return String.format("%.2f", num / K) + "K";
		}
		return String.format("%.0f", num)+" ";
	}


	public static class HeroStat {

		public int protoId;
		public String name;

		public int talent;
		public byte camp;
		public byte job;
		public long power;

		public long round;
		public long winTime;

		public double damage = 0;
		public double heal = 0;
		public double hurt = 0;
		public double kill = 0;
		public double dead = 0;
		public double move = 0;
		public double ctrlBuff = 0;
		public double alive = 0;

		public double winRate;
		public double avgDamage;
		public double avgHeal;
		public double avgHurt;
		public double avgKill;
		public double avgDead;
		public double avgMove;
		public double avgCtrl;
		public double avgAlive;


		public HeroStat(GameBody game, int protoId) {
			this.protoId = protoId;
			HeroBaseRow heroBaseRow = game.table.hero.base.get(protoId + 1);
			if (heroBaseRow != null) {
				name = heroBaseRow.foodNameNote;
				talent = heroBaseRow.talent;
				camp = heroBaseRow.camp;
				job = heroBaseRow.job;
			}
		}

		public void record(GameBody game, boolean win, HeroFighter fighter) {
			FighterPerformance performance = fighter.performance;
			round++;
			if (win) {
				winTime++;
			}
			damage += performance.damage;
			heal += performance.heal;
			hurt += performance.hurt;
			kill += performance.kill;
			dead += performance.dead;
			for (FighterPerformance.RoundPerformance round : performance.rounds) {
				move += round.skillCastMap.values().stream().mapToInt(Integer::intValue).sum();
				for (Map.Entry<Integer, Integer> buffEntry : round.buffAddMap.entrySet()) {
					int buffId = buffEntry.getKey() + 1;
					BuffRow buffRow = game.table.buff.base.get(buffId);
					if (buffRow == null) {
						continue;
					}
					if (GDBuff.Type.isCtrl(buffRow.effectType)) {
						ctrlBuff += buffEntry.getValue();
					}
				}
			}
			if (fighter.isAlive()) {
				alive++;
			}
		}

		public void stat() {
			winRate = winTime * 100d / round;
			avgDamage = damage / round;
			avgHeal = heal / round;
			avgHurt = hurt / round;
			avgKill = kill / round;
			avgDead = dead / round;
			avgMove = move / round;
			avgCtrl = ctrlBuff / round;
			avgAlive = alive * 100d / round;
		}
	}
}
